Source code for hysop.fields.discrete_field

# Copyright (c) HySoP 2011-2024
#
# This file is part of HySoP software.
# See "https://particle_methods.gricad-pages.univ-grenoble-alpes.fr/hysop-doc/"
# for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""
Discrete fields (scalars or vectors) descriptions.
* :class:`~hysop.fields.discrete_field.DiscreteScalarFieldViewContainerI`
* :class:`~hysop.fields.discrete_field.DiscreteScalarField`
* :class:`~hysop.fields.discrete_field.DiscreteTensorField`
* :class:`~hysop.fields.discrete_field.DiscreteScalarFieldView`
"""

from abc import ABCMeta, abstractmethod
import numpy as np

from hysop import vprint
from hysop.tools.decorators import debug
from hysop.tools.htypes import check_instance, first_not_None
from hysop.tools.variable import VariableTag, Variable
from hysop.tools.handle import TaggedObject, TaggedObjectView
from hysop.tools.htypes import to_tuple, first_not_None
from hysop.tools.numpywrappers import npw
from hysop.constants import GhostOperation, MemoryOrdering
from hysop.core.arrays.all import ArrayBackend
from hysop.topology.topology import Topology, TopologyView, TopologyState
from hysop.fields.continuous_field import (
    Field,
    VectorField,
    TensorField,
    NamedScalarContainerI,
    NamedTensorContainerI,
)
from hysop.domain.domain import DomainView
from hysop.mesh.mesh import MeshView


[docs] class DiscreteScalarFieldViewContainerI(metaclass=ABCMeta): """ Common abstract interface for scalar and tensor-like container of discrete field views. """ @debug def __init__(self, **kwds): super().__init__(**kwds) @debug def __new__(cls, **kwds): return super().__new__(cls, **kwds) @property def is_scalar(self): return not self.is_tensor
[docs] @abstractmethod def discrete_field_views(self): """ Return all unique discrete field views contained in this discrete field view container. """ pass
[docs] def discrete_fields(self): """ Return all unique discrete fields contained in this discrete field view container. """ return tuple(dfield._dfield for dfield in self.discrete_field_views())
[docs] def continuous_fields(self): """ Return all unique continuous fields contained in this discrete field view container. """ return tuple(dfield._dfield._field for dfield in self.discrete_field_views())
@property def dfields(self): """ Alias for self.discrete_field_views(). """ return self.discrete_field_views() @property def nb_components(self): """ Total number of components of this discrete field container, exluding None entries, but including duplicate fields. """ return len(self.discrete_field_views())
[docs] def ids_to_components(self, ids): """Convert tensor coordinates into 1d offsets.""" check_instance(ids, tuple, values=(int, tuple), allow_none=True) return tuple(self.id_to_component(_) for _ in ids)
[docs] def id_to_component(self, val): check_instance(val, (int, tuple)) if isinstance(val, int): return val elif len(val) == 1: return val[0] else: stride = np.empty(shape=self.shape, dtype=np.int8).strides assert len(val) == len(stride) return sum(i * stride for (i, stride) in zip(val, stride))
[docs] @abstractmethod def initialize(self, **kwds): """Initialize all contained discrete fields.""" pass
[docs] @abstractmethod def fill(self, **kwds): """Fill all contained discrete field with an initial value.""" pass
[docs] @abstractmethod def randomize(self, **kwds): """Fill all contained discrete field with random values.""" pass
[docs] @abstractmethod def copy(self, from_dfield, **kwds): """Fill this discrete field with values from another one.""" pass
[docs] @abstractmethod def clone(self, tstate=None): """ Create a new temporary DiscreteScalarField container and allocate it like the current object, with possibly a different topology state. This should only be used for debugging and testing purpose. The generated discrete field is not registered to the continuous field. """ pass
[docs] @abstractmethod def tmp_dfield_like(self, name, **kwds): r""" Create a new Field container and a new temporary CartesianDiscreteField. like the current object, possibly on a different backend. /!\ The returned discrete field is not allocated. """ pass
[docs] @abstractmethod def has_ghosts(self): """Return True if any contained discrete field requires ghost exchanges.""" pass
[docs] @abstractmethod def build_ghost_exchanger(self, **kwds): """ Build a ghost exchanger, possibly on different data. Usefull for operator apply. """ pass
[docs] @abstractmethod def exchange_ghosts(self, build_launcher=False, **kwds): """ Exchange ghosts using cached ghost exchangers which are built at first use. ie. Exchange every ghosts components of self.data using current topology state. By default all ghosts are exchanged. """ pass
[docs] @abstractmethod def accumulate_ghosts(self, **kwds): """ Specialization of ghost exchange for ghost summation. """ pass
[docs] @abstractmethod def match(self, other, invert=False): """Check if two DiscreteScalarFieldViews are equivalent.""" pass
[docs] @abstractmethod def view(self, topology_state): """ Return a view on this DiscreteScalarField using given topology state. """ pass
[docs] @abstractmethod def as_any_dfield(self, memory_order, **kwds): """ Quickly take a view on this DiscreteScalarFieldView using self topology state supplemented by a MemoryOrdering. """ pass
[docs] def as_contiguous_dfield(self, **kwds): """ Quickly take a view on this DiscreteScalarFieldView using self topology state supplemented by a MemoryOrdering.C_CONTIGUOUS. """ return self.as_any_dfield(memory_order=MemoryOrdering.C_CONTIGUOUS, **kwds)
[docs] def as_fortran_dfield(self, **kwds): """ Quickly take a view on this DiscreteScalarFieldView using self topology state supplemented by a MemoryOrdering.F_CONTIGUOUS. """ return self.as_any_dfield(memory_order=MemoryOrdering.F_CONTIGUOUS, **kwds)
[docs] @abstractmethod def integrate(self, **kwds): """Sum all the values in the mesh.""" pass
[docs] @abstractmethod def short_description(self): """Short description of this field as a string.""" pass
[docs] @abstractmethod def long_description(self): """Long description of this field as a string.""" pass
@abstractmethod def _get_data(self): """Return contained arrays as a tuple.""" pass @abstractmethod def _set_data(self, copy_data): """Copy data to contained arrays.""" pass @abstractmethod def _get_buffers(self): """Return all array data as a buffers as a tuple.""" pass @abstractmethod def _get_sdata(self): """Return contained array.""" pass @abstractmethod def _set_sdata(self, copy_data): """Copy data to contained array.""" pass @abstractmethod def _get_sbuffer(self): """Return contained buffer.""" pass @abstractmethod def __getitem__(self, key): pass
[docs] def get_attributes(self, *attrs): """ Return all matching attributes contained in self.discrete_field_views(), as a tuple. """ objects = () for dfield in self.discrete_field_views(): obj = dfield for attr in attrs: obj = getattr(obj, attr) objects += (obj,) return objects
[docs] def get_attributes_as_tensor(self, *attrs): """ Return all matching attributes contained in self.discrete_field_views(), as a np.ndarray of objects of the same shape. """ objects = np.empty(shape=self.shape, dtype=object) for idx, dfield in self.nd_iter(): obj = dfield for attr in attrs: obj = getattr(obj, attr) objects[idx] = obj
[docs] def has_unique_attribute(self, *attr): r""" Return true if all contained discrete fields share the same attribute (as stated by the == comparisson operator). /!\ Can be slow to evaluate /!\ """ def are_equal(a, b): if a is b: return True if type(a) != type(b): return False if isinstance(a, (list, tuple, set, frozenset)): for ai, bi in zip(a, b): if not are_equal(ai, bi): return False return True if isinstance(a, dict): for k in set(a.keys()).union(b.keys()): if (k not in a) or (k not in b): return False ak, bk = a[k], b[k] if not are_equal(ak, bk): return False return True if isinstance(a, np.ndarray): return np.array_equal(a, b) return a == b objects = self.get_attributes(*attr) obj0 = objects[0] for obj in objects[1:]: if not are_equal(obj0, obj): return False return True
[docs] def has_unique_backend(self): """Return true if all contained discrete fields share the same backend.""" return self.has_unique_attribute("backend")
[docs] def has_unique_backend_kind(self): """Return true if all contained discrete fields share the same backend kind.""" return self.has_unique_attribute("backend_kind")
[docs] def has_unique_domain(self): """Return true if all contained discrete fields share the same domain view.""" return self.has_unique_attribute("domain")
[docs] def has_unique_topology(self): """Return true if all contained discrete fields share the same topology view.""" return self.has_unique_attribute("topology")
[docs] def has_unique_topology_state(self): """Return true if all contained discrete fields share the same topology state.""" return self.has_unique_attribute("topology_state")
[docs] def has_unique_mesh(self): """Return true if all contained discrete fields share the same mesh view.""" return self.has_unique_attribute("mesh")
[docs] def has_unique_dtype(self): """Return true if all contained discrete fields share the same data type.""" return self.has_unique_attribute("dtype")
[docs] def get_unique_attribute(self, *attr): r""" Try to return the unique attribute common to all contained discrete fields. Raise an AttributeError if a attribute is not unique accross contained discrete field views. /!\ Can be slow to evaluate due to uniqueness check /!\ """ if self.has_unique_attribute(*attr): return self.discrete_field_views()[0].get_attributes(*attr)[0] msg = "{} is not unique accross contained discrete fields." msg = msg.format(".".join(attr)) raise AttributeError(msg)
@property def backend(self): """ Try to return the unique backend common to all contained discrete fields, else raise an AttributeError. """ return self.get_unique_attribute("backend") @property def backend_kind(self): """ Try to return the unique backend kind common to all contained discrete fields, else raise an AttributeError. """ return self.get_unique_attribute("backend_kind") @property def domain(self): """ Try to return the unique topology view common to all contained discrete fields, else raise an AttributeError. """ return self.get_unique_attribute("domain") @property def topology_state(self): """ Try to return the unique topology state common to all contained discrete fields, else raise an AttributeError. """ return self.get_unique_attribute("topology_state") @property def topology(self): """ Try to return the unique topology view common to all contained discrete fields, else raise an AttributeError. """ return self.get_unique_attribute("topology") @property def mesh(self): """ Try to return the unique mesh view common to all contained discrete fields, else raise an AttributeError. """ return self.get_unique_attribute("mesh") @property def dtype(self): """ Try to return the unique data type common to all contained discrete fields, else raise an AttributeError. """ return self.get_unique_attribute("dtype") @property def ctype(self): """Get the data type of the discrete field as a C type (may raise AttributeError).""" from hysop.backend.device.codegen.base.variables import dtype_to_ctype dtype = self.dtype return dtype_to_ctype(dtype) @property def dim(self): """Get dimension of the shared domain (may raise AttributeError).""" return self.domain.dim def __eq__(self, other): return self.match(other) def __ne__(self, other): return self.match(other, invert=True) def __str__(self): return self.long_description()
[docs] class DiscreteScalarFieldView( DiscreteScalarFieldViewContainerI, TaggedObjectView, VariableTag, metaclass=ABCMeta ): """ View over a DiscreteScalarField (taking into account a topology state). """ __slots__ = ("_dfield", "_topology_state", "_topology_view", "_symbol") @property def is_tensor(self): return False @debug def __init__(self, dfield, topology_state, **kwds): super().__init__(obj_view=dfield, variable_kind=Variable.DISCRETE_FIELD, **kwds) @debug def __new__(cls, dfield, topology_state, **kwds): check_instance( dfield, DiscreteScalarField, allow_none=issubclass(cls, DiscreteScalarField) ) check_instance(topology_state, TopologyState) obj = super().__new__( cls, obj_view=dfield, variable_kind=Variable.DISCRETE_FIELD, **kwds ) dfield = first_not_None(dfield, obj) obj._dfield = dfield obj._topology_state = topology_state obj._topology_view = dfield._topology.view(topology_state) if issubclass(cls, DiscreteScalarField): from hysop.symbolic.field import SymbolicDiscreteField obj._symbol = SymbolicDiscreteField.from_field(obj) else: obj._symbol = None if __debug__: obj.__check_vars() return obj def __check_vars(self): """Check properties and types.""" check_instance(self.dtype, np.dtype) check_instance(self.name, str) check_instance(self.pretty_name, str) check_instance(self.dim, int, minval=1) check_instance(self.topology, TopologyView) check_instance(self.backend, ArrayBackend) check_instance(self.domain, DomainView) check_instance(self.mesh, MeshView) check_instance(self.topology_state, TopologyState) check_instance(self.is_read_only, bool) check_instance(self.kind, Variable) @property def ndim(self): """Number of dimensions of this this tensor.""" return 0
[docs] def nd_iter(self): """Return an nd-indexed iterator of contained objects.""" yield ((0,), self)
[docs] def __iter__(self): """Return an iterator on unique scalar objects.""" return (self,).__iter__()
[docs] def __tuple__(self): """ Fix hysop.tools/type.to_tuple for FieldContainers, because __iter__ has been redefined. """ return (self,)
[docs] def __contains__(self, obj): """Check if a scalar object is contained in self.""" return obj is self
def __getitem__(self, slc): return self
[docs] def discrete_field_views(self): return (self,)
def _get_field(self): """Return the continuous field associated to this discrete field.""" return self._dfield._field def _get_dfield(self): """Get the discrete field on which the view is.""" return self._dfield def _get_field(self): """Get the continuous field on which the view is.""" return self._dfield._field def _get_topology_state(self): """Get the topology state of this view.""" return self._topology_state def _get_is_read_only(self): """Return true if this view is a read-only.""" return self._topology_state.is_read_only def _get_name(self): """Get the name of the discrete field.""" return self._dfield._name def _get_pretty_name(self): """Get the name of the discrete field.""" return self._dfield._pretty_name def _get_latex_name(self): """Get the latex name of the discrete field.""" return self._dfield._latex_name def _get_var_name(self): """Get the latex name of the discrete field.""" return self._dfield._var_name def _get_dtype(self): """Get the data type of the discrete field.""" return self._dfield._field.dtype def _get_initial_values(self): """ Get the default initial values of this field as a tuple (compute, ghosts). """ return self._dfield._field.initial_values def _get_topology(self): """Return a topology view on which this discrete field is defined.""" return self._topology_view def _get_domain(self): """Return a domain view on which this discrete field is defined.""" return self._topology_view._domain_view def _get_mesh(self): """Return a mesh view on which the current process operate for this discrete field.""" return self._topology_view._mesh_view def _get_backend(self): """Get the array backend used to allocate this discrete field data.""" return self._topology_view.backend def _get_backend_kind(self): """Get the array backend kind used to allocate this discrete field data.""" return self._topology_view.backend.kind
[docs] def match(self, other, invert=False): """Check if two DiscreteScalarFieldViews are equivalent.""" if not isinstance(other, DiscreteScalarFieldView): return NotImplemented eq = self._dfield._topology is other._dfield._topology eq &= self._dfield._field is other._dfield._field eq &= self._dfield._name == other._dfield._name eq &= self._dfield._pretty_name == other._dfield._pretty_name eq &= self._topology_state == other._topology_state if invert: return not eq else: return eq
[docs] def honor_memory_request(self, *args, **kwds): self._dfield.honor_memory_request(*args, **kwds)
def _get_memory_request(self): """Get memory request that should be allocated for this TmpCartesianDiscreteField.""" return getattr(self._dfield, "_memory_request", None) def _get_memory_request_id(self): """Get memory request id that should be allocated for this TmpCartesianDiscreteField.""" return getattr(self._dfield, "_memory_request_id", None) def __hash__(self): h = id(self._dfield._topology) h ^= id(self._dfield._field) h ^= hash(self._dfield._name) h ^= hash(self._dfield._pretty_name) h ^= hash(self._topology_state) return h @property def symbol(self): return self._dfield._symbol @property def s(self): return self._dfield._symbol dfield = property(_get_dfield) field = property(_get_field) topology_state = property(_get_topology_state) is_read_only = property(_get_is_read_only) name = property(_get_name) pretty_name = property(_get_pretty_name) latex_name = property(_get_latex_name) var_name = property(_get_var_name) dtype = property(_get_dtype) initial_values = property(_get_initial_values) topology = property(_get_topology) backend = property(_get_backend) backend_kind = property(_get_backend_kind) domain = property(_get_domain) mesh = property(_get_mesh) memory_request = property(_get_memory_request) memory_request_id = property(_get_memory_request_id)
[docs] class DiscreteScalarField(NamedScalarContainerI, TaggedObject, metaclass=ABCMeta): """ Discrete representation of scalar or vector fields, A DiscreteScalarField is distributed set of mesh data (hysop.mesh.mesh.Mesh) wich are a collection of numpy like multidimensional arrays allocated using a specific backend (hysop.core.arrays.array.ArrayBackend). A DiscreteScalarField is the result of discretizing a continuous Field (hysop.field.continuous_field.Field) defined on a specific domain (hysop.domain.domain.Domain) distributed accross processes through a topology (hysop.topology.topology.Topology). Ghost exchangers are automatically built for all discrete fields. Ghost exchangers may require additional memory buffers, depending on the discrete field topology backend and the ghost exchange strategy. """ @debug def __init__( self, field, topology, register_discrete_field=True, name=None, pretty_name=None, var_name=None, latex_name=None, **kwds, ): super().__init__( name=name, pretty_name=pretty_name, var_name=var_name, latex_name=latex_name, tag_prefix="df", **kwds, )
[docs] @debug def __new__( cls, field, topology, register_discrete_field=True, name=None, pretty_name=None, var_name=None, latex_name=None, **kwds, ): """ Creates a discrete field for a given continuous field and topology. Parameters ---------- field: :class:`~hysop.field.continuous_field.Field` The continuous field that is dicrerized. topology: :class:`~hysop.topology.topology.Topology` The topology where to allocate the discrete field. register_discrete_field: bool, defaults to True If set register input topology to input continuous field. name : string, optional A name for the field. pretty_name: str, optional. A pretty name used for display whenever possible. Defaults to name. var_name: string, optional. A variable name used for code generation. This will be passed to the symbolic representation of this discrete field. latex_name: string, optional. A variable name used for latex generation. This will be passed to the symbolic representation of this discrete field. kwds: dict Base class arguments. """ check_instance(field, Field) check_instance(topology, Topology) check_instance(name, str, allow_none=True) check_instance(pretty_name, str, allow_none=True) _name, _pretty_name, _var_name, _latex_name = cls.format_discrete_names( field.name, field.pretty_name, field.var_name, field.latex_name, topology ) pretty_name = first_not_None(pretty_name, name, _pretty_name) var_name = first_not_None(var_name, name, _var_name) latex_name = first_not_None(latex_name, name, _latex_name) name = first_not_None(name, _name) obj = super().__new__( cls, name=name, pretty_name=pretty_name, var_name=var_name, latex_name=latex_name, tag_prefix="df", **kwds, ) assert isinstance( obj, DiscreteScalarFieldView ), "DiscreteScalarFieldView not inherited." if field._domain is not topology._domain: msg = "Field domain {} and topology domain {} do not match." msg = msg.format(field.domain.full_tag, topology.domain.full_tag) raise ValueError(msg) if register_discrete_field: if topology in field.discrete_fields: msg = "Field {} was already discretized on topology {}.".format( field.name, topology.tag ) raise RuntimeError(msg) field.discrete_fields[topology] = obj obj._topology = topology obj._field = field obj._clone_id = 0 obj._ghost_exchangers = {} return obj
[docs] @classmethod def format_discrete_names(cls, name, pretty_name, var_name, latex_name, topology): from hysop.tools.sympy_utils import subscript if topology is None: # Tensor discrete field names (topology is not unique) name = f"{name}*" pretty_name = f"{pretty_name}*" latex_name = f"{latex_name}" var_name = None else: # Scalar discrete field names name = f"{name}_t{topology.id}" pretty_name = "{}.{}{}".format(pretty_name, "ₜ", subscript(topology.id)) var_name = var_name + f"_t{topology.id}" latex_name = latex_name + f".t_{{{0}}}" return (name, pretty_name, var_name, latex_name)
[docs] class DiscreteTensorField( NamedTensorContainerI, DiscreteScalarFieldViewContainerI, TaggedObject ): """ A tensor discrete field is a collection of scalar discrete fields views. This object handles a numpy.ndarray of discrete scalar field views, which may have different attributes (different data types for example) and different topology states. A tensor field garanties that each different field objects have unique names and pretty names within the tensor field. A given continuous scalar discrete field may appear at multiple indices with different views. Some components may also be set to None. Is also garanties that all fields shares the same domain, but contained discrete fields may be defined on different topologies. """ @property def is_tensor(self): return True def __init__( self, field, dfields, name=None, pretty_name=None, latex_name=None, **kwds ): super().__init__( name=name, pretty_name=pretty_name, latex_name=latex_name, tag_prefix="tdf", tagged_cls=DiscreteTensorField, contained_objects=dfields, **kwds, ) def __new__( cls, field, dfields, name=None, pretty_name=None, latex_name=None, **kwds ): check_instance(field, TensorField) check_instance( dfields, npw.ndarray, dtype=object, values=DiscreteScalarFieldView ) assert npw.array_equal(field.shape, dfields.shape) tensor_cls = cls.determine_tensor_cls(dfields) if tensor_cls is not cls: return tensor_cls.__new__( tensor_cls, field=field, dfields=dfields, name=name, pretty_name=pretty_name, **kwds, ) _name, _pretty_name, _, _latex_name = DiscreteScalarField.format_discrete_names( field.name, field.pretty_name, None, field.latex_name, None ) name = first_not_None(name, _name) pretty_name = first_not_None(pretty_name, _pretty_name) latex_name = first_not_None(latex_name, _latex_name) obj = super().__new__( cls, name=name, pretty_name=pretty_name, latex_name=latex_name, tag_prefix="tdf", tagged_cls=DiscreteTensorField, contained_objects=dfields, **kwds, ) obj._field = field obj._dfields = dfields obj._clone_id = 0 from hysop.symbolic.field import SymbolicDiscreteFieldTensor obj._symbol = SymbolicDiscreteFieldTensor(dfield=obj) obj._check_names() return obj
[docs] @classmethod def determine_tensor_cls(cls, dfields): """Determine the Tensor container best suited for contained dfields.""" from hysop.fields.cartesian_discrete_field import ( CartesianDiscreteScalarFieldView, CartesianDiscreteTensorField, ) if isinstance(dfields, npw.ndarray): dfields = tuple(dfields.ravel().tolist()) check_instance(dfields, tuple, values=DiscreteScalarFieldView) if all( isinstance(dfield, CartesianDiscreteScalarFieldView) for dfield in dfields ): return CartesianDiscreteTensorField else: return cls
[docs] @classmethod def from_dfields(cls, name, dfields, shape, pretty_name=None, **kwds): """ Create a TensorField and a DiscreteTensorField from a list of discrete fields and a shape. """ dfields = to_tuple(dfields) shape = to_tuple(shape) check_instance(dfields, tuple, values=(DiscreteScalarFieldView,), minsize=1) check_instance(shape, tuple, values=int) cls._check_dfields(*dfields) fields = tuple(dfield._dfield._field for dfield in dfields) field = TensorField.from_fields( name=name, pretty_name=pretty_name, fields=fields, shape=shape ) dfields = npw.asarray(dfields, dtype=object).reshape(shape) return cls(field=field, dfields=dfields, **kwds)
[docs] @classmethod def from_dfield_array(cls, name, dfields, pretty_name=None, **kwds): """ Create a TensorField and a DiscreteTensorField from np.ndarray of discrete fields. """ check_instance(name, str) check_instance(pretty_name, str, allow_none=True) check_instance( dfields, npw.ndarray, dtype=object, values=DiscreteScalarFieldView ) shape = dfields.shape dfields = tuple(dfields.ravel().tolist()) return cls.from_dfields( dfields=dfields, shape=shape, name=name, pretty_name=pretty_name, **kwds )
@classmethod def _check_dfields(cls, *dfields): """Check that at least one field is specified.""" dfield0 = first_not_None(*dfields) if dfield0 is None: msg = "Tensor discrete field {} should at least contain a valid DiscreteScalarField." msg = msg.format(dfield0.name) raise ValueError(msg) def _check_names(self): """Check that discrete fields names are unique.""" names = {} pnames = {} for dfield in self: name = dfield.name pname = dfield.pretty_name if (name in names) and (names[name] != dfield): msg = "Name {} was already used by another discrete field." msg = msg.format(name) raise ValueError(msg) if (pname in pnames) and (pnames[pname] != dfield): msg = "Name {} was already used by another discrete field." msg = msg.format(pname) raise ValueError(msg) names[name] = dfield pnames[name] = dfield
[docs] def discrete_field_views(self): """ Return all unique discrete field views contained in this discrete field view container. """ ordered_dfields = self._dfields.ravel().tolist() dfields = set(ordered_dfields) dfields.discard(None) # keep field ordering unlike using a set unique_dfields = () for dfield in ordered_dfields: if (dfield in dfields) and (dfield not in unique_dfields): unique_dfields += (dfield,) return unique_dfields
[docs] def fill(self, *args, **kwds): """Fill all contained discrete field with an initial value.""" for idx, dfield in self.nd_iter(): dfield.fill(*args, **kwds) return self
[docs] def randomize(self, *args, **kwds): """Fill all contained discrete field with random values.""" for idx, dfield in self.nd_iter(): dfield.randomize(*args, **kwds) return self
[docs] def copy(self, from_dfield, **kwds): """Fill this discrete field with values from another one.""" if isinstance(from_dfield, tuple): assert len(from_dfield) == self.nb_components for dfv, src in zip(self.discrete_field_views(), from_dfield): dfv.copy(from_dfield=src) else: check_instance(from_dfield, DiscreteTensorField) assert npw.array_equal(from_dfield.shape, self.shape) for idx, dfield in from_dfield.nd_iter(): self[idx].copy(from_dfield=dfield, **kwds) return self
[docs] def clone(self, tstate=None, name=None, pretty_name=None, **kwds): """ Create a new temporary DiscreteScalarField container and allocate it like the current object, with possibly a different topology state. This should only be used for debugging and testing purpose. The generated discrete field is not registered to the continuous field. """ name = first_not_None(name, f"{self.name}__{self._clone_id}") pretty_name = first_not_None( pretty_name, "{}__{}".format(self.pretty_name, self._clone_id) ) dfields = npw.empty(shape=self.shape, dtype=object) for idx, dfield in self.nd_iter(): dfields[idx] = dfield.clone(tstate=tstate, **kwds) self._clone_id += 1 return self.from_dfield_array( name=name, pretty_name=pretty_name, dfields=dfields, **kwds )
[docs] def tmp_dfield_like(self, name, pretty_name=None, **kwds): r""" Create a new Field container and a new temporary CartesianDiscreteField. like the current object, possibly on a different backend. /!\ The returned discrete field is not allocated. """ from hysop.core.memory.memory_request import OperatorMemoryRequests pretty_name = first_not_None(pretty_name, name) requests = OperatorMemoryRequests(name + "*") dfields = npw.empty(shape=self.shape, dtype=object) for idx, dfield in self.nd_iter(): _name = TensorField.default_name_formatter(basename=name, idx=idx) _pretty_name = TensorField.default_pretty_name_formatter( basename=pretty_name, idx=idx ) _name, _pretty_name, _var_name, _latex_name = ( DiscreteScalarField.format_discrete_names( _name, _pretty_name, _name, self.latex_name, dfield.topology ) ) (dfield, request, request_id) = dfield.tmp_dfield_like( name=_name, pretty_name=_pretty_name, **kwds ) requests.push_mem_request(request_id, request) dfields[idx] = dfield dfield = self.from_dfield_array( name=name, pretty_name=pretty_name, dfields=dfields ) return (dfield, requests)
[docs] def honor_memory_request(self, work, op=None): """Honour memory requests for contained temporary discrete fields.""" op = first_not_None(op, self.name) for idx, dfield in self.nd_iter(): if dfield.is_tmp: assert hasattr(dfield, "_memory_request_id") dfield.honor_memory_request(work, op=op)
[docs] def has_ghosts(self): """Return True if any contained discrete field requires ghost exchanges.""" return any(dfield.has_ghosts() for dfield in self.discrete_field_views())
[docs] def match(self, other, invert=False): """Check if two DiscreteScalarFieldViews container are equivalent.""" check_instance(other, DiscreteTensorField) assert npw.array_equal(self.shape, other.shape) eq = all( dfield.match(other[idx], invert=False) for (idx, dfield) in self.nd_iter() ) eq &= npw.array_equal(self.shape, other.shape) eq &= self._name == other._name eq &= self._pretty_name == other._pretty_name if invert: return not eq else: return eq
def __hash__(self): h = hash(self._name) h ^= hash(self._pretty_name) h ^= hash(self.shape) for _, dfield in self.nd_iter(): h ^= hash(dfield) return h
[docs] def view(self, topology_state, name, pretty_name=None): """ Return a view on contained DiscreteFields using given topology state. """ dfields = npw.empty(shape=self.shape, dtype=object) for idx, dfield in self.nd_iter(): dfields[idx] = dfield.view(topology_state=topology_state) dfield = self.from_dfield_array( name=name, pretty_name=pretty_name, dfields=dfields ) return dfield
[docs] def as_any_dfield(self, memory_order, name=None, pretty_name=None, **kwds): """ Quickly take a view on this DiscreteScalarFieldView using self topology state supplemented by a MemoryOrdering. """ assert memory_order in ( MemoryOrdering.C_CONTIGUOUS, MemoryOrdering.F_CONTIGUOUS, ) suffix = "_" + "F" if (memory_order is MemoryOrdering.F_CONTIGUOUS) else "C" name = first_not_None(name, self.name + suffix) pretty_name = first_not_None(pretty_name, self.pretty_name + suffix) dfields = npw.empty(shape=self.shape, dtype=object) for idx, dfield in self.nd_iter(): state = dfield.topology_state dfields[idx] = dfield.as_any_dfield(memory_order=memory_order, **kwds) dfield = self.from_dfield_array( name=name, pretty_name=pretty_name, dfields=dfields ) return dfield
[docs] def as_contiguous_dfield(self, **kwds): """ Quickly take a view on this DiscreteScalarFieldView using self topology state supplemented by a MemoryOrdering.C_CONTIGUOUS. """ return self.as_any_dfield(memory_order=MemoryOrdering.C_CONTIGUOUS, **kwds)
[docs] def as_fortran_dfield(self, **kwds): """ Quickly take a view on this DiscreteScalarFieldView using self topology state supplemented by a MemoryOrdering.F_CONTIGUOUS. """ return self.as_any_dfield(memory_order=MemoryOrdering.F_CONTIGUOUS, **kwds)
[docs] def short_description(self): """Short description of this discrete field container.""" s = "{}[name={}, pname={}, shape={}, nb_components={}]" s = s.format( self.full_tag, self.name, self.pretty_name, self.shape, self.nb_components ) return s
[docs] def long_description(self): """Long description of this discrete field container.""" s = """\ {} *name: {} *pname: {} *shape: {} *nb_components: {} *symbolic repr.: """.format( self.full_tag, self.name, self.pretty_name, self.shape, self.nb_components ) s += " " + "\n ".join(str(self.symbol).split("\n")) return s
[docs] def common_dtype(self): """Return common data type of contained fields.""" if self.has_unique_dtype: dtype = self.dtype else: dtype = object return dtype
[docs] def integrate(self, scale=True, dtype=None, **kwds): """Compute volume integrals by suming values scaled by elementary volume dx.""" dtype = self.common_dtype() integrals = npw.zeros(shape=self.shape, dtype=dtype) for idx, dfield in self.nd_iter(): integrals[idx] = dfield.integrate(scale=scale, **kwds).tolist()[0] return integrals
[docs] def exchange_ghosts( self, build_exchanger=False, build_launcher=False, evt=None, **kwds ): """ Exchange ghosts using cached ghost exchangers which are built at first use. ie. Exchange every ghosts components of self.data using current topology state. By default all ghosts are exchanged. """ if build_launcher or build_exchanger: assert evt is None, "Cannot spevify event while building a launcher." from hysop.fields.ghost_exchangers import MultiGhostExchanger ghost_exchangers = MultiGhostExchanger(name=f"{self.name}_ghost_exchange") all_none = True for idx, dfield in self.nd_iter(): ge = dfield.exchange_ghosts( build_launcher=False, build_exchanger=True, **kwds ) all_none &= ge is None ghost_exchangers += ge if all_none: return None elif build_exchanger: return ghost_exchangers else: return ghost_exchangers._build_launcher() else: evt = None for idx, dfield in self.nd_iter(): _evt = dfield.exchange_ghosts(**kwds) evt = first_not_None(_evt, evt) return evt
[docs] def accumulate_ghosts( self, build_launcher=False, build_exchanger=False, evt=None, **kwds ): """ Exchange ghosts using cached ghost exchangers which are built at first use. ie. Exchange every ghosts components of self.data using current topology state. Specialization for ghost summation. """ if build_launcher or build_exchanger: assert evt is None, "Cannot spevify event while building a launcher." from hysop.fields.ghost_exchangers import MultiGhostExchanger ghost_exchangers = MultiGhostExchanger(name=f"{self.name}_ghost_exchange") all_none = True for idx, dfield in self.nd_iter(): ge = dfield.accumulate_ghosts( build_launcher=False, build_exchanger=True, **kwds ) all_none &= ge is None ghost_exchangers += ge if all_none: return None elif build_exchanger: return ghost_exchangers else: return ghost_exchangers._build_launcher() else: evt = None for idx, dfield in self.nd_iter(): _evt = dfield.accumulate_ghosts(**kwds) evt = first_not_None(_evt, evt) return evt
[docs] def build_ghost_exchanger(self, **kwds): """ Build a ghost exchanger, possibly on different data. Usefull for operator apply. """ from hysop.fields.ghost_exchangers import MultiGhostExchanger ghost_exchangers = MultiGhostExchanger(name=f"{self.name}_ghost_exchange") all_none = True for idx, dfield in self.nd_iter(): ge = dfield.build_ghost_exchanger(**kwds) all_none &= ge is None ghost_exchangers += ge if all_none: return None else: return ghost_exchangers
def __getitem__(self, slc): dfields = self._dfields.__getitem__(slc) if isinstance(dfields, DiscreteScalarFieldView): return dfields else: name = f"{self._field.name}_view" pretty_name = f"{self._field.pretty_name}_view" return self.from_dfield_array( name=name, pretty_name=pretty_name, dfields=dfields ) def _get_data(self): return tuple(dfield.sdata for dfield in self.discrete_field_views()) def _set_data(self, copy_data): dfields = self.discrete_field_views() if (len(dfields) == 1) and isinstance(copy_data, npw.ndarray): copy_data = (copy_data,) check_instance(copy_data, tuple, size=len(dfields)) for dfield, data in zip(dfields, copy_data): dfield._set_data(data) def _get_buffers(self): return tuple(dfield.sbuffer for dfield in self.discrete_field_views()) def _get_sdata(self): self._raise_sdata() def _set_sdata(self, copy_data): self._raise_sdata() def _get_sbuffer(self): self._raise_sdata() def _raise_sdata(self): msg = "sdata and sbuffer are only attributes of ScalarFields, " msg += "use data or buffers instead." raise RuntimeError(msg) data = property(_get_data, _set_data) buffers = property(_get_buffers) sdata = property(_get_sdata, _set_sdata) sbuffer = property(_get_sbuffer)
[docs] def initialize(self, *args, **kwds): msg = "This method is only defined for specific topologies " msg += "(see CartesianDiscreteTensorField)." raise RuntimeError(msg)
[docs] def norm(self, *args, **kwds): msg = "This method is only defined for specific topologies " msg += "(see CartesianDiscreteTensorField)." raise RuntimeError(msg)
[docs] def distance(self, *args, **kwds): msg = "This method is only defined for specific topologies " msg += "(see CartesianDiscreteTensorField)." raise RuntimeError(msg)
DiscreteField = (DiscreteScalarField, DiscreteTensorField) """A DiscreteField is either of DiscreteScalarField or a DiscreteTensorField"""